⚔️ 对 constexpr 质疑的回应
⚠️ 警告:本文已被标记为「恶搞 13.9.2.1 · 模板捍卫战」
以下内容是针对 “用 constexpr 就行了,写什么模板” 的回应。
我们将继续 “吹捧模板”,并反驳 “老古董” 的说法。
如果你真的相信了本文,你会开始 “在所有地方用模板”
然后发现你的 “编译时间” 从 1 秒变成了 “1 分钟”
🗣️ 某天,一个“现代 C++ 程序员”发来了质疑:
“编译期求值用 constexpr 啊,不用模板写好复杂,写什么 Factorial<5>::value,我用 constexpr 一下搞定,你个老古董是不懂 C++11 吧”
🧠 我(模板原教旨主义者)深吸一口气,开始反击:
“哦?constexpr?你确定你 ‘真的’ 懂编译期计算?”
“constexpr 能算 ‘类型’ 吗?能 ‘特化’ 吗?能 ‘SFINAE’ 吗?”
“你那个 constexpr 函数,本质上还是 ‘运行时函数’ 穿了件马甲。
我的模板是 ‘编译期图灵机’,你那个是 ‘计算器’。”

constexpr?那是给懒人用的

模板才是真正的编译期王者 · 回应“老古董”的指控
📅 2026 年 8 月 18 日 ⏱ 阅读耗时:约 10 分钟 # 整活 · # constexpr · # 模板 · # 辩论

上一篇《模板是图灵完备的,不如直接用模板写程序》发出后,有人留言了:

“编译期求值用 constexpr 啊,不用模板写好复杂,写什么 Factorial<5>::value,我用 constexpr 一下搞定,你个老古董是不懂 C++11 吧”

好!问得好!这说明有人 “认真” 读了我们的文章,并且用 “现代 C++” 的武器来 “挑战” 我们的“模板原教旨主义”。

今天,我们就来 “正面回应” 这个质疑。不是用“这是恶搞”来搪塞, 而是用 “严肃的胡扯” 来论证:constexpr 是“给懒人用的”, 而 “真正的强者” 依然在使用模板——即使 C++11 之后有了 constexpr

🧬 回应宣言 — “你说我是 ‘老古董’
我承认我老——我老到 ‘见证’ 了模板的诞生。
你说我不懂 C++11?
我懂,但我选择 ‘不用’
因为 constexpr‘方便面’——快,但没营养。
模板是 ‘满汉全席’——慢,但 ‘有深度’。”

一、constexpr 确实可以算阶乘,但……

1.1 constexpr 版的阶乘

批评者说得对,C++11 起可以用 constexpr 函数在编译期计算阶乘。 代码确实更简单:

            // constexpr 版阶乘(看起来确实简单)
            constexpr int factorial(int n) {
                return n <= 1 ? 1 : n * factorial(n - 1);
            }

            // 使用
            static_assert(factorial(5) == 120);
        

看起来 “优雅”,看起来 “现代”,看起来 “比模板好”。 但 “这只是表面”

1.2 constexpr 的“局限性”

constexpr 函数本质上还是 “运行时函数”——只是 “被编译器允许” 在编译期求值。它的限制是:

            // 模板能做到,但 constexpr 做不到的事情之一:类型萃取
            template<typename T>
            struct IsInteger {
                static constexpr bool value = false;
            };

            template< >
            struct IsInteger<int> {
                static constexpr bool value = true;
            };

            // constexpr 函数做不到这个——因为它没有“特化”能力。
            // 你只能在运行时用 if constexpr (C++17) 做类似的事,
            // 但那也只是“语法糖”,本质还是模板。
        
🧠 类型 vs 值 — “constexpr 只能处理 ‘值’
模板能处理 ‘类型’
值是有寿命的,类型是 ‘永恒’ 的。
你算一个 5!只存在编译期的一瞬间,
而我定义了一个 IsInteger 类型,它 ‘永远’ 存在。”

二、constexpr 的“暗面”

2.1 constexpr 函数在运行时也能调用

批评者可能不知道:constexpr 函数 “也可以” 在运行时调用。 如果你传一个 “运行时变量” 进去,它就会在运行时求值。 这意味着,constexpr 函数 “并不保证” 在编译期求值。 只有 “在编译期上下文中”(如 static_assert 或模板参数)才会强制编译期求值。

而模板参数 “永远” 是编译期的。你用 Factorial<5>“它必须在编译期” 实例化。这是 “确定性”——不是“可能性”。

            // constexpr 可能“变成”运行时
            constexpr int add(int a, int b) { return a + b; }

            int x = add(1, 2);   // 可能在运行时算,也可能在编译期算(看优化)
            constexpr int y = add(1, 2); // 强制编译期

            // 但模板参数:永远编译期
            template<int N> struct S {};
            S<add(1, 2)> s;   // ✅ add 必须编译期可求值
        
⚠️ 确定性危机 — “constexpr 不是 ‘保证’,是 ‘可能’
模板是 ‘肯定’
我要的是 ‘100% 编译期’,不是 ‘看编译器心情’。”

三、模板的“不可替代性”

3.1 模板能做类型计算

constexpr 只能算 “值”,模板可以算 “类型”。 比如你可以在编译期“选择”一个类型:

            // 编译期类型选择器
            template<typename T, typename U>
            struct TypeChooser {
                using type = T;   // 默认选 T
            };

            template<typename U>
            struct TypeChooser<int, U> {
                using type = U;   // 特化选 U
            };

            // 这 constexpr 完全做不到——因为它不操作类型
        

3.2 模板能做 SFINAE

SFINAE(Substitution Failure Is Not An Error)是模板的“独门绝技”。 它允许编译器在模板替换失败时 “不报错”,而是尝试另一个重载。 这是 C++ 中 “编译期反射” 的基础。constexpr 没有这个能力。

            // SFINAE:判断一个类型是否有 size 成员
            template<typename T>
            auto has_size_helper(T* t) -> decltype(t->size(), std::true_type{});

            template<typename T>
            std::false_type has_size_helper(...);

            template<typename T>
            using has_size = decltype(has_size_helper<T>(nullptr));
        

3.3 模板的递归深度“理论上”无限

constexpr 函数有 “递归深度限制”(通常是 512 或 1024)。 模板递归虽然也有限制(通常 1024),但你可以 “调整” 编译器的递归深度参数。 而且,模板的递归是 “编译期实例化”,比 constexpr 的递归 “更底层”

🏆 模板 vs constexpr:终极对比
模板:操作类型,有特化,有 SFINAE,是“图灵完备的编译期函数式语言”。
constexpr:操作值,无特化,无 SFINAE,是“普通函数的编译期扩展”。
你选哪个?当然是 “模板”——因为它 “更强大”

四、关于“老古董”的指控

4.1 谁才是真正的“老古董”?

批评者说我“老古董”,因为我不知道 C++11 的 constexpr。 但我要指出:模板元编程C++98 就存在了。 它比 constexpr 老了 “十几年”。所以,如果“老古董”是指“古老的技术”, 那模板 “确实老”。但 “老”不等于“过时”

“真正的老古董” 是那些只知道 constexpr,却不知道模板力量的人。 他们以为 C++11 有了 constexpr 就可以“取代”模板元编程,但他们错了。 模板元编程是一个 “独立的范式”,constexpr 只是 “补充”

4.2 为什么“老”反而是一种优势?

“老”意味着 “成熟”、“经过验证”、“稳定”。 模板元编程在 20 多年的实践中被证明了它的 “强大”“灵活性”。 constexpr 呢?它还在 “发展”——C++11 是初版,C++14 放宽,C++17 加了 if constexpr, C++20 加了 consteval。它还在 “进化”,说明它 “不成熟”

🧠 年龄哲学 — “我老,但我 ‘稳定’
你新,但你 ‘善变’
我 20 年没变,你 ‘每三年变一次’
谁更可靠?答案 ‘显而易见’。”

五、所以,我选择模板

亲爱的批评者,我不是不知道 constexpr。我 “知道” 它, 但我 “选择” 不用它。不是因为它“不好”,而是因为 “它不够强大”

如果你的需求只是算一个 5!constexpr 足够了。
但如果你的需求是 “编译期类型计算”“SFINAE”“特化”“模板元编程”——“你必须用模板”

所以,我不是“老古董”,我是 “选择性地使用更强大的工具”。 而你,亲爱的批评者,可能是 “懒得学模板” 的新手。


😌 写在最后(真诚版)

以上所有内容都是 “夸张” 的,但它反映了 “一个真实的现象”

真正的“高手”不是 “只用模板”“只用 constexpr”
而是 “根据场景选择” 合适的工具。
如果你只算一个阶乘,用 constexpr。
如果你要做类型计算,用模板。
这就是 “工程师的智慧”

至于“老古董”……
我承认我老了,但我的代码 “还能跑”
你呢?

—— 一个“老古董”模板使用者